import { toUtf8 } from "../utils/util";
import { Type } from "./proto";

export interface CodecType {
    size(): number;
    encode(buf: number[], index: number): void,
    decode(buf: number[], index: number): void
};

export class Uint8 implements CodecType {

    value: number;

    constructor(v: number = 0) {
        this.value = v;
    }

    size(): number {
        return 1;
    }
    encode(buf: number[], index: number): void {
        buf[index] = this.value;
    }
    decode(buf: number[], index: number): void {
        this.value = buf[index];
    }
};

export class Uint16 implements CodecType {

    value: number;
    constructor(v: number = 0) {
        this.value = v;
    }
    size(): number {
        return 2;
    }
    encode(buf: number[], index: number): void {
        buf[index] = (this.value >> 8) & 0xFF;
        buf[index + 1] = this.value & 0xFF;
    }
    decode(buf: number[], index: number): void {
        this.value = (buf[index] << 8) + buf[index + 1];
    }
}

export class Uint32 implements CodecType {
    value: number;
    constructor(v: number = 0) {
        this.value = v;
    }
    size(): number {
        return 4;
    }
    encode(buf: number[], index: number): void {
        buf[index] = (this.value >> 24) & 0xFF;
        buf[index + 1] = (this.value >> 16) & 0xFF;
        buf[index + 2] = (this.value >> 8) & 0xFF;
        buf[index + 3] = this.value & 0xFF;
    }
    decode(buf: number[], index: number): void {
        this.value = (buf[index] << 24) +
            (buf[index + 1] << 16) +
            (buf[index + 2] << 8) +
            buf[index + 3];
    }
}

export class ByteView implements CodecType {
    value: number[];
    offset: number;
    len: number;

    constructor(value: number[] = [], offset: number = 0, len: number = 0) {
        this.value = value;
        this.offset = offset;
        this.len = len;
    }
    size(): number {
        return this.len + 1;
    }
    encode(buf: number[], index: number): void {
        buf[index] = this.len & 0xFF;
        for (let i = 0; i < this.len; i ++) {
            buf[index + i + 1] = this.value[i + this.offset];
        }
    }
    decode(buf: number[], index: number): void {
        this.value = buf;
        this.offset = index + 1;
        this.len = buf[index];
    }

    toString() {
        return toUtf8(this.value.slice(this.offset, this.offset + this.len)); 
    }
}

export function parse(buf: number[], ...args: CodecType[]) {
    let index = 3;
    for (const arg of args) {
        arg.decode(buf, index);
        index += arg.size();
    }
}

const HEAD0 = 0xE1;
const HEAD1 = 0x1E;
const END = 0xEF;

function make(...args: CodecType[]) {
    let len = 5;
    for (const arg of args) {
        len += arg.size();
    }
    const buf = new Array<number>(len);
    buf[0] = HEAD0;
    buf[1] = HEAD1;
    buf[2] = len;
    let index = 3;
    for (const arg of args) {
        arg.encode(buf, index);
        index += arg.size();
    }
    let sum = 0;
    for (let i = 3; i < index; i ++) {
        sum ^= buf[i];
    }
    buf[index] = sum;
    buf[index + 1] = END;
    return buf;
}

export function makeSimpleReq(seq: number, cmd: number, ...args: CodecType[]) {
    return make(new Uint8(Type.SIMPLE_REQ), new Uint8(seq), new Uint8(cmd), ...args);
}










